import { isUndefined, isNil, assign, isString, isNumber, isArray, forEach } from 'lodash'
/**
 * 存储工具类
 * @class
 * @alias module:utils/storage
 * @param {string} type 存储类型，支持session及local
 * @param {string} name 数据库名
 **/
function Storage (type, name) {
  this.type = type || 'session'

  this.name = name

  this.typeMapping = {}
}

// 注册原型方法
assign(Storage.prototype, {
  /**
   * 检查是否支持sessionStorage及localStorage
   *
   * @return {boolean} 是否支持本地存储
   * @throws throw an exception if not supported.
   */
  support: function () {
    if (isUndefined(window.sessionStorage) || isUndefined(window.localStorage)) {
      throw new Error('sessionStorage|localStorage is not supported!')
    }
    return true
  },

  /**
   * 获取指标
   *
   * 注：get操作会删除一次性指标及过期的指标
   *
   * @param  {string} key  指标名称
   * @param  {string} type 指标类型
   * @return {*} 指标值，如果设置了类型或者查询指定了类型，则尝试返回对应类型的数据，否则返回字符串
   */
  get: function (key, type) {
    this.support()

    var target = (this.name || '') + '@' + key
    var storage = this.type === 'session' ? window.sessionStorage : window.localStorage

    var value = storage.getItem(target)
    if (isNil(value)) {
      return value
    }

    var onceUsed = /;#once=true/.test(value)
    value = value.replace(';#once=true', '')

    var expiresed = /;#expires=(\d+)/.exec(value)
    if (expiresed) {
      var expires = Number(expiresed[1])
      if (expires <= (new Date()).getTime()) {
        this.delete(key)
        return null
      }
      value = value.replace(expiresed[0], '')
    }

    if (!isNil(type) || this.isDefined(key)) {
      type = type || this.typeMapping[key]
      try {
        switch (type) {
          case 'boolean':
            value = Boolean(value)
            break
          case 'number':
            value = Number(value)
            break
          case 'array':
          case 'object':
            value = JSON.parse(value)
            break
        }
      } catch (e) {
        value = null
      }
    }

    onceUsed && this.delete(key)
    return value
  },

  /**
   * 设置指标
   *
   * @param {string} key 指标名
   * @param {*} value 指标值
   * @param {number} expires 过期时间，单位毫秒，可缺省
   * @param {string} type 指标类型，可缺省
   * @return {none} 无返回值
   */
  set: function (key, value, expires, type) {
    this.support()

    if (!isString(value)) {
      value = JSON.stringify(value)
    }

    var target = (this.name || '') + '@' + key
    var storage = this.type === 'session' ? window.sessionStorage : window.localStorage

    if (isString(expires)) {
      this.typeMapping[key] = expires
      expires = null
    }
    if (!isNil(type)) {
      this.typeMapping[key] = type
    }

    if (isNumber(expires) && expires > 0) {
      expires = (new Date()).getTime() + (expires >> 0)
      value = value + ';#expires=' + expires
    }
    return storage.setItem(target, value)
  },

  /**
   * 删除指标
   *
   * @param {string} key 指标名
   * @return {none} 无
   */
  delete: function (key) {
    this.support()

    var target = (this.name || '') + '@' + key
    var storage = this.type === 'session' ? window.sessionStorage : window.localStorage
    storage.removeItem(target)
    if (this.typeMapping[target]) {
      delete this.typeMapping[target]
    }
  },

  clear: function () {
    this.support()

    forEach(this.typeMapping, (v, k) => {
      this.delete(k)
    })
  },

  /**
   * 追加指标 <br/>
   * 对于map类型，与原始值合并 <br/>
   * 对于array类型，新数据压入源数组 <br/>
   * 对于其他类型，执行+操作
   *
   * @param {string} key 指标名称
   * @param {*} value 追加值
   * @param {number} expires 过期时间
   * @return {none} 无
   */
  append: function (key, value, expires) {
    this.support()

    var nextVal = value
    if (this.isConfigured(key)) {
      var preVal = this.get(key)

      if (preVal === null) {
        return
      }

      if (this.isDefined(key)) {
        var type = this.typeMapping[key]
        if (type === 'object') {
          nextVal = assign({}, preVal, value || {})
        } else if (type === 'array') {
          isArray(preVal) && Array.prototype.push.apply(preVal, isArray(value) ? value : [value])
          nextVal = preVal
        } else {
          nextVal = preVal + nextVal
        }
      } else {
        nextVal = preVal + nextVal
      }
    }
    this.set(key, nextVal, expires)
  },

  /**
   * 只使用一次
   *
   * 注：只使用一次的不能追加数据
   *
   * @param {string} key 指标名称
   * @param {*} value 指标值
   * @param {number} expires 有效期，单位ms
   * @param {string} type 指标类型
   * @return {none} 无
   */
  once: function (key, value, expires, type) {
    this.support()

    var target = (this.name || '') + '@' + key
    var storage = this.type === 'session' ? window.sessionStorage : window.localStorage

    if (!isString(value)) {
      value = JSON.stringify(value)
    }

    if (isString(expires)) {
      this.typeMapping[key] = expires
      expires = null
    }
    if (!isNil(type)) {
      this.typeMapping[key] = type
    }

    value = value + ';#once=true'
    if (isNumber(expires) && expires > 0) {
      expires = (new Date()).getTime() + (expires >> 0)
      value = value + ';#expires=' + expires
    }

    return storage.setItem(target, value)
  },

  /**
   * 指标是否已设置
   *
   * @param {string} key 指标名称
   * @return {boolean} 对应指标是否存在
   */
  isConfigured: function (key) {
    this.support()

    var target = (this.name || '') + '@' + key
    var storage = this.type === 'session' ? window.sessionStorage : window.localStorage
    return !isNil(storage.getItem(target))
  },

  /**
   *
   * 是否定义了指标类型
   *
   * @param {string} key 指标名称
   * @return {boolean} 是否设置了对应指标的类型
   */
  isDefined: function (key) {
    this.support()

    return !isNil(this.typeMapping[key])
  },

  /**
   * 定义指标类型
   *
   * @param {string} key 指标名称
   * @param {string} type 指标类型
   * @return {none} 无
   **/
  define: function (key, type) {
    this.support()

    this.typeMapping[key] = type || 'string'
  }
})

// 支持的指标类型
Storage.TYPE = {
  STRING: 'string',
  NUMBER: 'number',
  BOOLEAN: 'boolean',
  ARRAY: 'array',
  OBJECT: 'object'
}

/**
 * 存储Helper类
 * @alias module:utils/storage
 */
function StorageHelper () {

}

/**
 * 初始化工厂方法
 *
 * @example
 * Storage.init({
 *   name: 'test',   // 数据库名，可缺省
 *   type: 'local',  // 数据库类型，默认为session
 *   def: {  // 预置指标类型声明['array', 'object', 'number', 'boolean', 'string']
 *     ids: 'array',
 *     item: 'object'
 *     score: 'number'
 *   },
 *   data: {  // 初始化数据
 *     ids: ['a', 'b'],
 *     item: {a: 'a', b: 'b'}
 *   }
 * })
 *
 * @param {map} config 配置
 * @return {Storage} 存储类实例
 */
StorageHelper.init = function (config = { type: 'session', name: '' }) {
  var storage = new Storage(config.type, config.name)

  forEach(config.def, function (type, key) {
    storage.define(key, type)
  })

  forEach(config.data, function (value, key) {
    storage.set(key, value)
  })

  return storage
}

const session = StorageHelper.init()
const local = StorageHelper.init({ type: 'local' })

export { StorageHelper }
export { session }
export { local }
